Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Add `executor` option to `BashOptions` that makes a `tools` proxy available
to JavaScript code running in js-exec. Tool calls are synchronous from the
QuickJS sandbox's perspective — they block via SharedArrayBuffer/Atomics
while the host resolves them asynchronously.
Natively integrates with `@executor/sdk` — the `setup` callback receives
the SDK instance for adding OpenAPI, GraphQL, and MCP sources that
auto-discover tools. `onToolApproval` controls which tools are allowed.
## Native SDK integration (OpenAPI, GraphQL, MCP)
```ts
import { Bash } from "just-bash";
const bash = new Bash({
executor: {
setup: async (sdk) => {
await sdk.sources.add({
kind: "openapi",
endpoint: "https://petstore3.swagger.io/api/v3",
specUrl: "https://petstore3.swagger.io/api/v3/openapi.json",
name: "petstore",
});
await sdk.sources.add({
kind: "graphql",
endpoint: "https://countries.trevorblades.com/graphql",
name: "countries",
});
await sdk.sources.add({
kind: "mcp",
endpoint: "https://mcp.example.com/sse",
name: "internal",
transport: "sse",
});
},
onToolApproval: async (request) => {
// Auto-approve reads, require confirmation for writes/deletes
if (request.operationKind === "read") return { approved: true };
const ok = await promptUser(
`Allow ${request.toolPath} (${request.operationKind})?`
);
return ok ? { approved: true } : { approved: false, reason: "denied" };
},
},
});
await bash.exec(`js-exec -c '
const pets = await tools.petstore.findPetsByStatus({ status: "available" });
const country = await tools.countries.country({ code: "US" });
const docs = await tools.internal.searchDocs({ query: "deploy" });
console.log(pets.length, "pets,", country.name, ",", docs.hits.length, "docs");
'`);
```
## Inline tools (no SDK needed)
```ts
const bash = new Bash({
executor: {
tools: {
"math.add": {
description: "Add two numbers",
execute: (args) => ({ sum: args.a + args.b }),
},
"db.query": {
execute: async (args) => {
const rows = await pg.query(args.sql);
return { rows };
},
},
},
},
});
await bash.exec(`js-exec -c '
const sum = await tools.math.add({ a: 3, b: 4 });
console.log(sum.sum);
const data = await tools.db.query({ sql: "SELECT * FROM users" });
for (const row of data.rows) console.log(row.name);
'`);
```
## Both: inline tools + SDK sources
```ts
const bash = new Bash({
executor: {
tools: {
"util.timestamp": {
execute: () => ({ ts: Math.floor(Date.now() / 1000) }),
},
},
setup: async (sdk) => {
await sdk.sources.add({ kind: "openapi", endpoint: "...", specUrl: "...", name: "api" });
},
onToolApproval: "allow-all",
},
});
```
## Implementation
- New INVOKE_TOOL (400) opcode in the SharedArrayBuffer bridge protocol
- SyncBackend.invokeTool() — worker-side sync call via Atomics.wait
- BridgeHandler accepts optional invokeTool callback, handles new opcode
- Worker registers __invokeTool native function + tools Proxy when
hasExecutorTools is set
- Tool invoker threads from BashOptions → Bash → InterpreterOptions →
InterpreterContext → CommandContext → js-exec → BridgeHandler → worker
- executor.setup lazily initializes @executor/sdk on first exec
(dynamic import — SDK is only loaded when setup is provided)
- executor.onToolApproval wired through to createExecutor() — controls
approval for SDK-discovered tools (inline tools are always allowed)
- SDK's CodeExecutor runtime delegates to js-exec's executeForExecutor
- Full executor mode (log capture + result capture) available via
executorMode flag for direct SDK integration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add
executoroption toBashOptionsthat gives JavaScript code running injs-execaccess to atoolsproxy for calling external tools, and auto-generates bash CLI commands from those tools so they work directly from the shell.Tool calls are synchronous from the QuickJS sandbox's perspective — they block via
SharedArrayBuffer/Atomicswhile the host resolves them asynchronously.Integrates with
@executor-js/sdkand the official plugins:@executor-js/plugin-graphql— auto-discovers tools from GraphQL schemas via introspection@executor-js/plugin-openapi— auto-discovers tools from OpenAPI specsAuto-generated CLI commands
Every tool automatically becomes a bash command using the namespace + subcommand pattern (like
git commit,gh pr create):Input modes (Speakeasy-style three-tier precedence)
Help output (gh CLI style)
camelCase → kebab-case with aliases
Opt-out
js-exec tool proxy (unchanged)
Tools are also available from JavaScript code via the
toolsproxy:GraphQL tool discovery
Offline introspection (no network call during setup):
OpenAPI tool discovery
Tool approval and elicitation
Credentials (never exposed to sandbox)
ExecutorConfig API
toolsRecord<string, { description?; execute }>setup(sdk) => Promise<void>pluginsany[]onToolApproval"allow-all" | "deny-all" | callback"allow-all"onElicitationhandler | "accept-all"exposeToolsAsCommandsbooleantrueSupported source kinds
"graphql"@executor-js/plugin-graphqlendpoint,name,introspectionJson?,headers?"openapi"@executor-js/plugin-openapispec,endpoint,name,headers?"custom"name,tools: { [name]: { execute } }Implementation details
CLI command generation (new):
src/commands/tool-command.ts— argument parser, help formatter, namespace command factory--json> stdin (modeled after Speakeasy CLI)ghCLI-style help output (USAGE/COMMANDS/FLAGS/EXAMPLES)camelToKebab()for subcommand names, with original camelCase aliasesbuildNamespaceCommands()groups tools by first dot-segmentensureExecutorReady()Tool invocation bridge:
INVOKE_TOOL(400) opcode in the SharedArrayBuffer bridge protocolSyncBackend.invokeTool()— worker-side sync call viaAtomics.wait__invokeToolnative function +toolsProxy whenhasExecutorToolsis setSDK integration:
executor.setuplazily initializes@executor-js/sdkon firstexec()(dynamic import)sources.add()dispatches bykind:"graphql"→ plugin-graphql,"openapi"→ plugin-openapi,"custom"→ discovery pluginintrospectionJsonexec()Security:
undefinedonElicitationdefaults to declining all requestsheadersconfig never reach the sandboxTest plan